/*
 * Copyright (C) 2011 The Open Source Project
 * Copyright 2014 Manabu Shimobe
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ms.square.ohos.expandabletextview;

import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.*;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.app.Context;
import ohos.hiviewdfx.HiLog;
import ohos.hiviewdfx.HiLogLabel;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Rect;
import ohos.media.image.common.Size;

import java.io.InputStream;
import java.util.Map;

public class ExpandableTextView extends DirectionalLayout implements
        Component.EstimateSizeListener, Component.ClickedListener, ComponentContainer.ArrangeListener {
    private static final HiLogLabel logLabel = new HiLogLabel(HiLog.LOG_APP, 0x00101, "ExpandableTextView");

    private static final int EXPAND_INDICATOR_IMAGE_BUTTON = 0;

    private static final int EXPAND_INDICATOR_TEXT_VIEW = 1;

    private static final int DEFAULT_TOGGLE_TYPE = EXPAND_INDICATOR_IMAGE_BUTTON;

    /* The default number of lines */
    private static final int MAX_COLLAPSED_LINES = 4;

    /* The default animation duration */
    private static final int DEFAULT_ANIM_DURATION = 300;

    /* The default alpha value when the animation starts */
    private static final float DEFAULT_ANIM_ALPHA_START = 0.7f;

    private boolean mRelayout = true;

    // Show short version as default.
    private boolean mCollapsed = true;

    private int mCollapsedHeight;

    private double singleTextHeight;
    /**
     * 内容展示Text
     */
    private Text expandText;

    /**
     * 展开，收缩图标
     * View to expand/collapse
     */
    private Component expandCollapse;

    /**
     * 最大展示行数
     */
    private int maxCollapsedLines;

    private int mMarginBetweenTxtAndBottom;

    /**
     * 文本是否可以被点击
     */
    private boolean expandToggleOnTextClick;


    private ExpandIndicatorController mExpandIndicatorController;

    /**
     * 动画展示时长
     */
    private int animationDuration;

    /**
     * 起始透明度
     */
    private float animAlphaStart = 0.7f;

    private OnExpandStateChangeListener mListener;

    /**
     * 收缩状态
     */
    private Map<Integer, Boolean> collapsedStatus;

    private int mPosition;
    private int mStartHeight;
    private int expendTextHeight;

    /**
     * 构造函数
     *
     * @param context Context
     */
    public ExpandableTextView(Context context) {
        super(context);
    }

    /**
     * 构造函数
     *
     * @param context Context
     * @param attrSet AttrSet
     */
    public ExpandableTextView(Context context, AttrSet attrSet) {
        super(context, attrSet);
        init(attrSet);
    }

    /**
     * 构造函数
     *
     * @param context   Context
     * @param attrSet   AttrSet
     * @param styleName styleName
     */
    public ExpandableTextView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        init(attrSet);
    }

    /**
     * 设置布局方向（只支持竖向）
     *
     * @param orientation 布局方向
     */
    @Override
    public void setOrientation(int orientation) {
        if (Component.HORIZONTAL == orientation) {
            throw new IllegalArgumentException("ExpandableTextView only supports Vertical Orientation.");
        }
        super.setOrientation(orientation);
    }

    AnimatorValue animation = new AnimatorValue();

    @Override
    public void onClick(Component component) {
        if (animation.isRunning()) {
            return;
        }
        if (expandCollapse.getVisibility() != Component.VISIBLE) {
            return;
        }

        mCollapsed = !mCollapsed;
        mExpandIndicatorController.changeState(mCollapsed);

        // 记录伸缩状态
        if (collapsedStatus != null) {
            collapsedStatus.put(mPosition, mCollapsed);
        }

        if (mStartHeight == 0) {
            mStartHeight = getHeight();
            expendTextHeight = expandText.getHeight();
            expandText.setHeight(expendTextHeight);
            expandText.setMaxTextLines(Integer.MAX_VALUE);
        }

        // 动效
        animation.setDuration(animationDuration);
        animation.setStateChangedListener(new Animator.StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
                applyAlphaAnimation(expandText, animAlphaStart);
            }

            @Override
            public void onStop(Animator animator) {

            }

            @Override
            public void onCancel(Animator animator) {

            }

            @Override
            public void onEnd(Animator animator) {
                // notify the listener
                mListener.expandStateChangedToast(!mCollapsed);
            }

            @Override
            public void onPause(Animator animator) {

            }

            @Override
            public void onResume(Animator animator) {

            }
        });
        animation.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {

            @Override
            public void onUpdate(AnimatorValue animatorValue, float interpolatedTime) {
                final int newHeight;
                if (mCollapsed) {
                    newHeight = (mCollapsedHeight - getHeight()) * (int) interpolatedTime + getHeight();
                } else {
                    double mEndHeight = mStartHeight + singleTextHeight * 7 - expendTextHeight;
                    newHeight = ((int) mEndHeight - mStartHeight) * (int) interpolatedTime + mStartHeight;
                }
                expandText.setHeight(newHeight - mMarginBetweenTxtAndBottom);
                if (Float.compare(animAlphaStart, 1.0f) != 0) {
                    double result = (double) interpolatedTime * ((double)1 - (double)animAlphaStart);
                    double alphaResult = animAlphaStart + result;
                    applyAlphaAnimation(expandText, (float) alphaResult);
                }

                getContext().getUITaskDispatcher().delayDispatch(new Runnable() {
                    @Override
                    public void run() {
                        if (mListener != null) {
                            mListener.onExpandStateChanged(expandText);
                        }
                    }
                }, 10);
            }
        });

        animation.start();
    }

    @Override
    public boolean onEstimateSize(int widthEstimateConfig, int heightEstimateConfig) {
        HiLog.debug(logLabel, "onEstimateSize");
        singleTextHeight = getFontHeight(expandText.getTextSize());
        // If no change, measure and return
        if (!mRelayout || getVisibility() == Component.HIDE) {
            return false;
        }
        mRelayout = false;

        // Setup with optimistic case
        // i.e. Everything fits. No button needed
        expandCollapse.setVisibility(Component.HIDE);
        expandText.setMaxTextLines(Integer.MAX_VALUE);

        // Measure
        super.estimateSize(widthEstimateConfig, heightEstimateConfig);
        // If the text fits in collapsed mode, we are done.
        if (expandText.getMaxTextLines() <= maxCollapsedLines) {
            return false;
        }
        // Doesn't fit in collapsed mode. Collapse text view as needed. Show
        // button.
        if (mCollapsed) {
            expandText.setMaxTextLines(maxCollapsedLines);
        }
        expandCollapse.setVisibility(Component.VISIBLE);

        // Re-measure with new setup
        super.estimateSize(widthEstimateConfig, heightEstimateConfig);

        if (mCollapsed) {
            mMarginBetweenTxtAndBottom = getHeight() - expandText.getHeight();
            // Saves the collapsed height of this ViewGroup
            mCollapsedHeight = getEstimatedHeight();
        }
        return false;
    }


    /**
     * 获取一行的字体高度
     *
     * @param fontSize 字体大小
     * @return 行高
     */
    private double getFontHeight(int fontSize) {
        Paint paint = new Paint();
        paint.setTextSize(fontSize);
        Paint.FontMetrics fm = paint.getFontMetrics();
        return Math.ceil((double) fm.descent - (double) fm.top) + 2;
    }


    private void init(AttrSet attrs) {
        initAttrs(attrs);
        // enforces vertical orientation
        setOrientation(Component.VERTICAL);
        //add绘制的回调
        addDrawTask(mDrawTask);

    }

    //绘制的回调
    final private DrawTask mDrawTask = new DrawTask() {
        @Override
        public void onDraw(Component component, Canvas canvas) {
            //add测量的回调
            setEstimateSizeListener(ExpandableTextView.this);
            //add布局的回调
            setArrangeListener(ExpandableTextView.this);
            findViews();

        }
    };


    /**
     * 初始化属性
     *
     * @param attrSet 属性
     */
    private void initAttrs(AttrSet attrSet) {
        maxCollapsedLines = TypedAttrUtils.getInteger(attrSet, "maxCollapsedLines", MAX_COLLAPSED_LINES);
        animationDuration = TypedAttrUtils.getInteger(attrSet, "animDuration", DEFAULT_ANIM_DURATION);
        animAlphaStart = TypedAttrUtils.getFloat(attrSet, "animAlphaStart", DEFAULT_ANIM_ALPHA_START);
        expandToggleOnTextClick = TypedAttrUtils.getBoolean(attrSet, "expandToggleOnTextClick", true);
        mExpandIndicatorController = setupExpandToggleController(getContext(), attrSet);
    }

    private void findViews() {
        int count = getChildCount();
        if (count < 1) {
            return;
        }
        for (int i = 0; i < count; i++) {
            Component component = getComponentAt(i);
            if (component.getComponentDescription().toString().equals("expandable_text")) {
                expandText = (Text) component;
            } else if (component.getComponentDescription().toString().equals("expand_collapse")) {
                expandCollapse = component;
            }
        }
        if (expandToggleOnTextClick) {
            expandText.setClickedListener(this);
        } else {
            expandText.setClickedListener(null);
        }
        expandCollapse.setClickedListener(this);
        mExpandIndicatorController.setView(expandCollapse);
        mExpandIndicatorController.changeState(mCollapsed);

        // enforces vertical orientation
        setOrientation(Component.VERTICAL);
    }

    /**
     * 设置监听
     *
     * @param listener 监听器
     */
    public void setOnExpandStateChangeListener(OnExpandStateChangeListener listener) {
        mListener = listener;
    }

    @Override
    public boolean onArrange(int i, int i1, int i2, int i3) {
        return false;
    }

    public interface OnExpandStateChangeListener {
        /**
         * Called when the expand/collapse animation has been finished
         *
         * @param textView - TextView being expanded/collapsed
         */
        void onExpandStateChanged(Text textView);

        /**
         * 状态改变时的提示语
         *
         * @param isExpanded 是否是展开状态
         */
        void expandStateChangedToast(boolean isExpanded);
    }

    interface ExpandIndicatorController {
        void changeState(boolean collapsed);

        void setView(Component toggleView);
    }

    private static void applyAlphaAnimation(Component view, float alpha) {
        view.setAlpha(alpha);
    }

    /**
     * Image图标控制器
     */
    private static class ImageButtonExpandController implements ExpandIndicatorController {

        private final PixelMap mExpandDrawable;
        private final PixelMap mCollapseDrawable;

        private Image mImageButton;

        public ImageButtonExpandController(PixelMap expandDrawable, PixelMap collapseDrawable) {
            mExpandDrawable = expandDrawable;
            mCollapseDrawable = collapseDrawable;
        }

        @Override
        public void changeState(boolean collapsed) {
            mImageButton.setPixelMap(collapsed ? mExpandDrawable : mCollapseDrawable);
        }

        @Override
        public void setView(Component toggleView) {
            mImageButton = (Image) toggleView;
        }
    }

    /**
     * Text图标控制器
     */
    static class TextViewExpandController implements ExpandIndicatorController {

        private final int mExpandText;
        private final int mCollapseText;

        private Text mTextView;

        public TextViewExpandController(int expandText, int collapseText) {
            mExpandText = expandText;
            mCollapseText = collapseText;
        }

        @Override
        public void changeState(boolean collapsed) {
            mTextView.setText(collapsed ? mExpandText : mCollapseText);
        }

        @Override
        public void setView(Component toggleView) {
            mTextView = (Text) toggleView;
        }
    }

    private static PixelMap getPixelMap(int resId, Context context) {
        InputStream drawableInputStream = null;
        try {
            drawableInputStream = context.getResourceManager().getResource(resId);
            ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
            sourceOptions.formatHint = "image/png";
            ImageSource imageSource = ImageSource.create(drawableInputStream, null);
            ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
            decodingOptions.desiredSize = new Size(0, 0);
            decodingOptions.desiredRegion = new Rect(0, 0, 0, 0);
            decodingOptions.desiredPixelFormat = PixelFormat.ARGB_8888;
            PixelMap pixelMap = imageSource.createPixelmap(decodingOptions);
            return pixelMap;
        } catch (Exception e) {
            HiLog.debug(logLabel, "getPixelMap" + e.getMessage());
        } finally {
            try {
                if (drawableInputStream != null) {
                    drawableInputStream.close();
                }
            } catch (Exception e) {
                HiLog.debug(logLabel, "getPixelMap" + e.getMessage());
            }
        }
        return null;
    }

    private static ExpandIndicatorController setupExpandToggleController(Context context, AttrSet attrSet) {
        final int expandToggleType = TypedAttrUtils.getInteger(attrSet, "expandToggleType", DEFAULT_TOGGLE_TYPE);
        final ExpandIndicatorController expandIndicatorController;
        switch (expandToggleType) {
            case EXPAND_INDICATOR_IMAGE_BUTTON:
                PixelMap expandDrawable = null;
                PixelMap collapseDrawable = null;
                boolean expandPresent = attrSet.getAttr("expandIndicator").isPresent();
                if (expandPresent) {
                    expandDrawable = ((PixelMapElement) (attrSet.getAttr("expandIndicator").get().getElement())).getPixelMap();
                }
                boolean collapsePresent = attrSet.getAttr("collapseIndicator").isPresent();
                if (collapsePresent) {
                    collapseDrawable = ((PixelMapElement) (attrSet.getAttr("collapseIndicator").get().getElement())).getPixelMap();
                }
                if (expandDrawable == null) {
                    expandDrawable = getPixelMap(ResourceTable.Media_ic_expand_more_black_12dp, context);
                }
                if (collapseDrawable == null) {
                    collapseDrawable = getPixelMap(ResourceTable.Media_ic_expand_less_black_12dp, context);
                }
                expandIndicatorController = new ImageButtonExpandController(expandDrawable, collapseDrawable);
                break;
            case EXPAND_INDICATOR_TEXT_VIEW:
                expandIndicatorController = new TextViewExpandController(ResourceTable.String_expand_more, ResourceTable.String_expand_less);
                break;
            default:
                throw new IllegalStateException("Must be of enum: ExpandableTextView_expandToggleType, one of EXPAND_INDICATOR_IMAGE_BUTTON or EXPAND_INDICATOR_TEXT_VIEW.");
        }

        return expandIndicatorController;
    }

    /**
     * 设置文案
     *
     * @param text 文案内容
     */
    public void setText(String text) {
        mRelayout = true;
        findViews();
        expandText.setText(text);
        setVisibility(null == text ? Component.HIDE : Component.VISIBLE);
        postLayout();
    }
}
